package cn.zyl.common.redis.config;

import cn.zyl.common.redis.exception.RedisBaseException;
import cn.zyl.common.redis.beans.JedisConfigBean;
import cn.zyl.common.redis.constant.RedisConfigConstant;
import cn.zyl.common.redis.service.JedisConnectApi;
import cn.zyl.common.redis.service.impl.JedisClusterConnectApiImpl;
import cn.zyl.common.redis.service.impl.JedisConnectApiImpl;
import cn.zyl.demo.common.boot.constant.CommonBootConstant;
import cn.zyl.demo.common.constant.SymbolConstant;
import cn.zyl.demo.common.enums.RegexEnum;
import cn.zyl.demo.common.tools.ObjectTool;
import cn.zyl.demo.common.tools.StringTool;
import cn.zyl.demo.common.tools.ValidTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import redis.clients.jedis.*;

import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * JedisConfigList类说明:
 *
 * @author wsz
 * @version v1.0
 * @date 2020-08-05
 */
@Component
public class JedisConfigList {
    private static final Logger LOGGER = LoggerFactory.getLogger(JedisConfigList.class);
    private final Object syn = new Object();
    /** 连接配置信息*/
    private volatile Map<String, JedisConfigBean> configMap;
    /** 单机版连接处理*/
    private volatile Map<String, JedisPool> jedisPoolMap;
    /** 集群版连接处理 使用JedisCluster时，不需要手动释放连接(自动释放）*/
    private volatile Map<String, JedisCluster> jedisClusterMap;

    /**
     * 增加jedis连接配置项
     * @param bean 连接配置
     * @throws RedisBaseException 异常
     */
    public void add(JedisConfigBean bean) throws RedisBaseException{
        LOGGER.info("add(: {}",bean);
        if(null == bean){return;}
        if(null == configMap){
            synchronized (syn){
                configMap = new ConcurrentHashMap<>(CommonBootConstant.NUM_20);
                jedisPoolMap = new ConcurrentHashMap<>(CommonBootConstant.NUM_10);
                jedisClusterMap = new ConcurrentHashMap<>(CommonBootConstant.NUM_10);
            }
        }
        //存在map中的key
        String key;
        if(StringTool.isEmpty(bean.getClusterNodes())){
            //单机版
            assertHost(bean.getHost(),true);
            key = bean.getHost();
            if(null != bean.getPort()){
                key += (":"+bean.getPort().toString());
            }
            //未赋值的字段，进行初始化
            bean.init();
            bean.setClusterEnabled(false);
            configMap.put(key, bean);
        }else{
            //集群版
            bean.setClusterEnabled(true);
            String[] hosts = bean.getClusterNodes().split(SymbolConstant.COMMA_EN);
            String[] hostPort;
            JedisConfigBean clone;
            //未赋值的字段，进行初始化
            bean.init();
            for(String host:hosts){
                key = host;
                clone = ObjectTool.clone(bean);
                if(host.lastIndexOf(SymbolConstant.COLON_EN) < 0){
                    JedisConfigList.regexHost(host);
                    clone.setHost(host);
                }else{
                    hostPort = host.split(SymbolConstant.COLON_EN);
                    //校验ip,端口
                    JedisConfigList.regexHost(hostPort[0]);
                    JedisConfigList.regexPort(hostPort[1]);
                    clone.setHost(hostPort[0]);
                    clone.setPort(Integer.valueOf(hostPort[1]));
                }
                configMap.put(key, clone);
            }
        }
    }

    /**
     * 获取配置相关的map
     * @return map
     */
    public Map<String, JedisConfigBean> getConfigMap() {
        return configMap;
    }
    /**
     * 根据host获取相关配置
     * @param host ip地址或者 ip:port
     * @return 配置对象
     */
    public JedisConfigBean getConfig(String host){
        return configMap.get(host);
    }

    public JedisConnectApi getJedis(String host) throws RedisBaseException{
        assertHost(host,false);
        LOGGER.info("getJedis(: {}",host);
        JedisConfigBean bean = configMap.get(host);
        if(StringTool.isBlank(bean.getPassword())){bean.setPassword(null);}
        //连接池配置
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(bean.getPoolMaxActive());
        config.setMaxIdle(bean.getPoolMaxIdle());
        config.setMinIdle(bean.getPoolMinIdle());
        config.setMaxWaitMillis(bean.getPoolMaxWait());
        config.setBlockWhenExhausted(bean.getBlockWhenExhausted());
        config.setJmxEnabled(bean.getJmxEnabled());
        //对拿到的connection进行validateObject校验
        config.setTestOnBorrow(true);
        if(bean.getClusterEnabled()){
            //集群模式
            JedisCluster cluster;
            if(jedisClusterMap.containsKey(bean.getClusterNodes())){
                cluster = jedisClusterMap.get(bean.getClusterNodes());
            }else{
                String[] hosts = bean.getClusterNodes().split(SymbolConstant.COMMA_EN);
                JedisConfigBean clone;
                Set<HostAndPort> nodes = new LinkedHashSet<>();
                for(String h:hosts){
                    clone = configMap.get(h);
                    nodes.add(new HostAndPort(clone.getHost(), clone.getPort()));
                }
                cluster = new JedisCluster(nodes,bean.getTimeout(),bean.getSoTimeout(),bean.getMaxAttempts(),bean.getPassword(),config);
                jedisClusterMap.put(bean.getClusterNodes(), cluster);
            }
            LOGGER.info("JedisClusterConnectApiImpl(: {}",bean);
            return new JedisClusterConnectApiImpl(cluster,bean.getHost()+":"+bean.getPort());
        }
        JedisPool jedisPool;
        if(jedisPoolMap.containsKey(host)){
            jedisPool = jedisPoolMap.get(host);
        }else{
            jedisPool = new JedisPool(config,bean.getHost() ,bean.getPort() ,bean.getTimeout(),bean.getPassword());
            jedisPoolMap.put(host, jedisPool);
        }
        Jedis jedis = jedisPool.getResource();
        LOGGER.info("JedisConnectApiImpl(: {}",bean);
        return new JedisConnectApiImpl(jedis);
    }

    /**
     * 通过抛异常，检查相关信息
     * @param host 连接redis的IP地址
     * @param init 是否是初始化的校验
     */
    private void assertHost(String host,boolean init) throws RedisBaseException{
        JedisConfigList.assertHost(host);
        if(null == configMap){
            throw new RedisBaseException("host is not add");
        }
        //初始化，将跳过configMap的校验
        if(init){
            if(configMap.containsKey(host)){
                throw new RedisBaseException("host is exist");
            }
            return;
        }
        if(configMap.isEmpty()){
            throw new RedisBaseException("host is not init");
        }
        if(!configMap.containsKey(host)){
            throw new RedisBaseException("host is not find");
        }
    }
    /**
     * 通过抛异常，检查相关单机host信息
     * @param host 连接redis的IP地址
     */
    public static void assertHost(String host) throws RedisBaseException{
        if(StringTool.isEmpty(host)){
            throw new RedisBaseException("host is null");
        }
        if(host.lastIndexOf(SymbolConstant.COLON_EN) > 0){
            //字符串中存在符号:,进行提取ip、端口
            String[] hs = host.split(SymbolConstant.COLON_EN);
            if(!ValidTool.regex(hs[1], RegexEnum.NUMBER_POSITIVE)){
                throw new RedisBaseException(hs[1]+",port is not number");
            }
            regexPort(hs[1]);
            host = hs[0];
        }
        regexHost(host);
    }
    /**
     * 通过抛异常，检查相关ip地址信息
     * @param host 连接redis的IP地址
     */
    public static void regexHost(String host) throws RedisBaseException{
        if(!RedisConfigConstant.DEFAULT_HOST.equals(host) && !ValidTool.regex(host, RegexEnum.IP)){
            throw new RedisBaseException(host + ",host is not IP");
        }
    }
    /**
     * 通过抛异常，检查相关端口port信息
     * @param port 连接redis的端口
     */
    public static void regexPort(String port) throws RedisBaseException{
        if(!ValidTool.regex(port, RegexEnum.NUMBER_POSITIVE)){
            throw new RedisBaseException(port+",port is not number");
        }
    }
}
